`timescale 1ns / 1ps

module dcache_ctrl(
    input clk,
    input rst,
    input clk_mem,

    input [31:0] cache_req_addr,
    input [31:0] cache_req_data,
    input cache_req_wen,
    input cache_req_valid,
    output [31:0] cache_resp_data,
    output reg cache_resp_stall,

    output reg [31:0] mem_req_addr,
    output reg [31:0] mem_req_data,
    output reg mem_req_wen,
    output reg mem_req_valid,
    input mem_resp_valid,
    input [31:0] mem_resp_data,

    input [9:0] cache_debug_addr,
    output [31:0] cache_debug_data,
    output cache_debug_dirty_bit,
    output [7:0] cache_debug_tag_seq
);
    
    parameter BLK_NUM = 128;

    reg valid [BLK_NUM - 1:0];
    reg dirty [BLK_NUM - 1:0];
    reg [1:0] tag [BLK_NUM - 1:0];
    reg [1:0] offset [BLK_NUM - 1:0];

    wire [1:0] req_tag; 
    wire [6:0] req_index;
    wire [1:0] req_offset;

    parameter   S_IDLE = 3'b000, S_BACK = 3'b001, S_BACK_WAIT = 3'b010,
                S_FILL = 3'b011, S_FILL_WAIT = 3'b100;
    reg [2:0] state;
    
    assign req_tag = cache_req_addr[10:9];
    assign req_index = cache_req_addr[8:2];
    assign req_offset = cache_req_addr[1:0];

    wire cache_hit, cache_dirty;
    wire [31:0] cache_data_out;

    reg cache_data_wen;
    reg [6:0] cache_data_waddr;
    reg [31:0] cache_data_wdata;

    assign cache_resp_data = cache_data_out;


    assign cache_hit = ((tag[req_index] == req_tag) & valid[req_index] & cache_req_valid);
    assign cache_dirty = (dirty[req_index] & cache_req_valid);

    reg mem_ready, cache_fill;

    integer i;

    always @(posedge clk or posedge rst) begin
        if (rst) begin
            for (i = 0; i < BLK_NUM; i = i+1) begin
                tag[i] <= 0;
                valid[i] <= 0;
                dirty[i] <= 0;
                offset[i] <= 0;
            end
        end

        if (rst | ~cache_req_valid) begin 
            state <= S_IDLE;
            cache_resp_stall <= 1'b0;
            cache_data_wen <= 1'b0;
            cache_data_wdata <= 32'b0;
            cache_data_waddr <= 7'b0;
            mem_req_addr <= 32'b0;
            mem_req_data <= 32'b0;
            mem_req_valid <= 1'b0;
            mem_req_wen <= 1'b0;
            mem_ready <= 0;
            cache_fill <= 0;

        end 
        else 
            case (state)
                S_IDLE:
                begin 
                    if (cache_hit) begin 
                        cache_data_wen <= cache_req_wen;
                        cache_data_wdata <= cache_req_data;
                        if (dirty[req_index] == 1'b0) dirty[req_index] <= cache_req_wen;
                        valid[req_index] <= 1'b1;
                        tag[req_index] <= req_tag;
                        offset[req_index] <= req_offset;

                        cache_resp_stall <= 1'b0;
                        state <= S_IDLE;
                    end 
                    else if (cache_dirty) begin 
                        cache_resp_stall <= 1'b1;
                        state <= S_BACK;
                        mem_ready <= 1'b0;
                    end 
                    else begin 
                        cache_resp_stall <= 1'b1;
                        state <= S_FILL;
                        cache_fill <= 1'b0;
                    end 
                end

                S_BACK:
                    begin 
                        mem_req_addr <= {21'b0, tag[req_index], req_index, offset[req_index]};
                        mem_req_data <= cache_data_out;
                        mem_req_valid <= 1'b1;
                        mem_req_wen <= 1'b1;
                        mem_ready <= mem_resp_valid;
                        if (~mem_ready) state <= S_BACK;
                        else state <= S_BACK_WAIT;
                    end
                    
                
                S_BACK_WAIT: begin
                        state <= S_FILL;
                        cache_fill <= 1'b0;
                        mem_req_valid <= 1'b0;
                        mem_req_wen <= 1'b0;
                    end
                
                
                S_FILL: begin
                    if (~cache_fill) state <= S_FILL;
                    else state <= S_FILL_WAIT;
                    mem_req_addr <= cache_req_wen ? 0 : cache_req_addr;
                    mem_req_data <= cache_req_wen ? 0 : cache_req_data;
                    mem_req_valid <= cache_req_wen ? 1'b0 : 1'b1;
                    mem_req_wen <= 1'b0;
                    mem_ready <= mem_resp_valid;
                    if (mem_ready || cache_req_wen) begin     
                        cache_data_wen <= 1'b1;
                        cache_data_wdata <= cache_req_wen ? cache_req_data : mem_resp_data;
                        cache_fill <= 1'b1;
                    end 
                end
                
                S_FILL_WAIT: begin 
                        cache_fill <= 1'b0;
                        cache_resp_stall <= 1'b0;
                        state <= S_IDLE;
                        mem_req_valid <= 1'b0;
                        mem_req_wen <= 1'b0;
                        dirty[req_index] <= cache_req_wen;
                        valid[req_index] <= 1'b1;
                        tag[req_index] <= req_tag;
                        offset[req_index] <= req_offset;
                    end 
						  
					 
            endcase



    end


    dcache cache_data (

    /* port a for data write */
        .clka(clk), // input clka
        .rsta(rst), // input rsta
        .wea((cache_data_wen & cache_req_valid)), // input [0 : 0] wea
        .addra(req_index), // input [6 : 0] addra
        .dina(cache_data_wdata), // input [31 : 0] dina
        .douta(cache_data_out), // output [31 : 0] douta

    /* port b for data read */
        .clkb(clk), // input clkb
        .rstb(rst), // input rstb
        .web(1'b0), // input [0 : 0] web
        .addrb(cache_debug_addr[6:0]), // input [6 : 0] addrb
        .dinb(32'b0), // input [31 : 0] dinb
        .doutb(cache_debug_data)  // output [31 : 0] doutb

    );
    
    assign cache_debug_dirty_bit = dirty[cache_debug_addr];
    assign cache_debug_tag_seq = 
        {2'b0, tag[cache_debug_addr], 2'b0, offset[cache_debug_addr]};


endmodule
